using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;
using System.Data.Common;
using System.Text;
using System.Collections;
using System.Web.UI.HtmlControls;
using System.Globalization;
using System.Web.Script.Services;
using System.Web.Services;
using System.Text.RegularExpressions;
using ResourceBlender.DAL;

/// <summary>
/// A page for managing resources for applications
/// </summary>
public partial class ResourcesOnline : LocalPage
{
    /// <summary>
    /// Indexes of the controls in each dynamic table row on the 'Add Resource' form
    /// </summary>
    private static class AddFormControlIndexes {
        public const int txtTranslation = 1, // multiview->view->textbox
                         txtTranslationComment = 1,
                         btnFindSimilar = 2;
    }
    /// <summary>
    /// Index of the controls in each dynamic cell
    /// </summary>
    private static class SelectedRowControlIndexes {
        public const int txtTranslation = 1,
                         btnFindSimilar = 3,
                         btnGoogle = 4;
    }

    private const int editButtonColumNo = 3;
    private StringBuilder jQueryBuffer;

    /// <summary>
    /// Displays available applications and handles JavaScript for the full-window tranlation editor
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void Page_Load(object sender, EventArgs e)
    {
        jQueryBuffer = new StringBuilder();
        if(!Page.IsPostBack) {
            litAddAppLink.Text = Utils.FormatLink(Resources.UIStrings.AddAppLink, ResolveUrl("~/Applications.aspx"));
            litSourceCommentsInstructions.Text = Utils.FormatLink(Resources.UIStrings.SourceCommentsInstructions, "http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files");
            if(appSelector.Applications.Count > 0) {
                mltMain.ActiveViewIndex = 1;
            }
            Session.Remove("defaultLanguage");
            Session.Remove("colIndexes");
            Session.Remove("app");
            Session.Remove("ro_selectedLanguages");
        } else {
            Page.ClientScript.RegisterClientScriptInclude("json", ResolveUrl("~/js/jquery.json-1.3.min.js"));
            Page.ClientScript.RegisterClientScriptInclude("blockUI", ResolveUrl("~/js/jquery.block.UI.min.js"));
            Page.ClientScript.RegisterClientScriptInclude("resourcejs", ResolveUrl("~/js/resources.js"));
            Master.RenderJSArrayWithCliendIds(fullsizeholder, cboAddResourceId, btnAddMoreRows, btnCancelBulkAdd);
            if(mltMain.ActiveViewIndex == 2) {
                PopulateSelectedLanguages(true, false, false);
            }
        }
        pnlAddedElement.Visible = false;
    }

    /// <summary>
    /// Outputs the jQuery buffer to JavaScript
    /// Page_PreRender > Controls Pre_Render > Page_SaveStateComplete
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void Page_SaveStateComplete(object sender, EventArgs e) {
        jQueryBuffer.Insert(0, "var prm = Sys.WebForms.PageRequestManager.getInstance(); prm.add_endRequest(function() { \n");
        jQueryBuffer.Append("});\n");
        ScriptManager.RegisterStartupScript(this, typeof(Page), "jquery", jQueryBuffer.ToString(), true);
    }

    /// <summary>
    /// Populates the selected languages to different elements on the page
    /// </summary>
    /// <param name="addForm"><c>true</c> if textboxes should be added for each selected language to the 'New Element' form.</param>
    /// <param name="filtersComboLanguage"><c>true</c> if each language should be added to the 'Filters' combobox.</param>
    /// <param name="attachAddListeners"><c>true</c> if the bind statements for the 'find translation' buttons should be returned (needed for initial page load before an AJAX request has occured).</param>
    /// <returns><c>null</c> unless <c>attachAddListeners</c> was true, in which case the listener statements are returned</returns>
    private string PopulateSelectedLanguages(bool addForm, bool filtersComboLanguage, bool attachAddListeners) {
        tblAddResource.Rows.Clear();
        string defaultLanguage = GetSelectedApplication().DefaultLanguage;
        Dictionary<string, int> columnIndex = Session["colIndexes"] as Dictionary<string, int>;
        KeyValuePair<string, string>[] addColIndex;
        if(columnIndex == null) {
            columnIndex = new Dictionary<string, int>();
            List<string> tmpLanguages = appSelector.SelectedLanguages;
            tmpLanguages.Sort();
            tmpLanguages.Remove(defaultLanguage);
            tmpLanguages.Insert(0, defaultLanguage);
            string[] languages = tmpLanguages.ToArray();
            addColIndex = new KeyValuePair<string, string>[tmpLanguages.Count];
            for(int i = 0; i < languages.Length; i++) {
                addColIndex[i] = new KeyValuePair<string, string>(languages[i], Utils.GetSafeEnglishName(languages[i]));
            }
        } else {
            addColIndex = new KeyValuePair<string, string>[columnIndex.Count];
            foreach(KeyValuePair<string, int> lang in columnIndex) {
                addColIndex[lang.Value - editButtonColumNo - 1] = new KeyValuePair<string, string>(lang.Key, Utils.GetSafeEnglishName(lang.Key));
            }
        }
        Session["addColIndex"] = addColIndex;
        KeyValuePair<string, string> currentLanguage;
        StringBuilder listenerBuffer = attachAddListeners ? new StringBuilder() : null;
        if(filtersComboLanguage) {
            cboTranslationFilterLanguage.Items.Clear();
        }
        for(int i = 0; i < addColIndex.Length; i++) {
            currentLanguage = addColIndex[i];
            if(addForm) {
                TableRow row = new TableRow();
                tblAddResource.Rows.Add(row);
                TableCell celLanguage = new TableCell();
                    celLanguage.CssClass = "language";
                    Label lbl = new Label();
                        lbl.ID = "lblDyn" + currentLanguage.Key;
                        lbl.Text = currentLanguage.Value;
                        lbl.CssClass = "block mockLabel";
                    celLanguage.Controls.Add(lbl);
                row.Cells.Add(celLanguage);
                TableCell celResource = new TableCell();
                    Label lblTranslation = new Label();
                        lblTranslation.Text = "Translation";
                        lblTranslation.CssClass = "block plain";
                    celResource.Controls.Add(lblTranslation);
                    TextBox txtTranslation = new TextBox();
                        txtTranslation.ID = "txtDyn" + currentLanguage.Key;
                        lblTranslation.AssociatedControlID = txtTranslation.ID;
                        txtTranslation.TextMode = TextBoxMode.MultiLine;
                        txtTranslation.Rows = 4;
                        txtTranslation.EnableViewState = false;
                        txtTranslation.ValidationGroup = "addelement";
                        txtTranslation.CssClass = "block";
                    celResource.Controls.Add(txtTranslation);
                    HyperLink btnFindResource = new HyperLink();
                        btnFindResource.Text = "Find Similar Translations";
                        btnFindResource.CssClass = "findResource blockTrigger";
                        btnFindResource.ToolTip = string.Format(Resources.UIStrings.LookupResourcesUsingTranslation, currentLanguage.Value);
                        btnFindResource.NavigateUrl = "#";
                        btnFindResource.ID = "dynfind-" + currentLanguage.Key;
                    celResource.Controls.Add(btnFindResource);
                    Literal imgLoading = new Literal();
                        imgLoading.Text = "<img src=\"images/ajax-small.gif\" alt=\"" + Resources.UIStrings.Searching + "\" class=\"ajax ajax-small\" />";
                    celResource.Controls.Add(imgLoading);
                row.Cells.Add(celResource);
                if(currentLanguage.Key == defaultLanguage) {
                    RequiredFieldValidator val = new RequiredFieldValidator();
                        val.ControlToValidate = txtTranslation.ID;
                        val.ErrorMessage = Resources.Errors.DefaultLanguageRequired;
                        val.SetFocusOnError = true;
                        val.Display = ValidatorDisplay.Dynamic;
                        val.ValidationGroup = "addelement";
                    celLanguage.Controls.Add(val);
                }
                if(attachAddListeners) {
                    Utils.AssignFindSimilarListener(listenerBuffer, btnFindResource.ClientID, txtTranslation.ClientID, celResource.ClientID, currentLanguage.Key, true);
                }
                TableCell celComment = new TableCell();
                    Label lblTranslatorComment = new Label();
                        lblTranslatorComment.Text = Resources.UIStrings.TranslatorsComment;
                        lblTranslatorComment.CssClass = "block plain";
                    celComment.Controls.Add(lblTranslatorComment);
                    TextBox txtTranslatorComment = new TextBox();
                        txtTranslatorComment.ID = txtTranslation.ID + "-comment";
                        lblTranslatorComment.AssociatedControlID = txtTranslatorComment.ID;
                        txtTranslatorComment.TextMode = TextBoxMode.MultiLine;
                        txtTranslatorComment.Rows = 4;
                        txtTranslatorComment.EnableViewState = false;
                        txtTranslatorComment.ValidationGroup = "addelement";
                        txtTranslatorComment.CssClass = "block";
                    celComment.Controls.Add(txtTranslatorComment);
                row.Cells.Add(celComment);
            }
            if(filtersComboLanguage) {
                cboTranslationFilterLanguage.Items.Add(new ListItem(currentLanguage.Value, currentLanguage.Key));
            }
        }
        if(attachAddListeners) {
            Utils.AssignEditPOListener(listenerBuffer, btnAddEditPO.ClientID, txtAddElementComment.ClientID);
            listenerBuffer.AppendLine(string.Format("$('#{0}').bind('click', function() {{ addBulk(true); }});", lnkAddBulk.ClientID));
            jQueryBuffer.Append(listenerBuffer.ToString()); // for subsequent loads with AJAX
            return listenerBuffer.ToString(); // for the initial load
        } else {
            return null;
        }
    }

    /// <summary>
    /// Gets the selected application, using the Session cache if available.
    /// </summary>
    /// <returns>A cached copy of the selected application, using the Session cache if available.</returns>
    private RBApplication GetSelectedApplication() {
        RBApplication app = Session["app"] as RBApplication;
        if(app == null) { // session expired
            app = ResourceBlenderDAL.GetApplication(appSelector.SelectedApplicationID, false);
            Session["app"] = app;
        }
        return app;
    }

    /// <summary>
    /// Gets the selected languages, using the Session cache if available, sorted by english name with the default language for the selected application first.
    /// </summary>
    /// <returns>A cached copy of the selected languages</returns>
    private string[] GetSelectedLanguages() {
        string[] displayedLanguages = Session["ro_selectedLanguages"] as string[];
        if(displayedLanguages == null) {
            bool includedDefaultLanguage = false; // Check if the default language is selected in the list. Add it if not
            string defaultLanguage = GetSelectedApplication().DefaultLanguage;
            Dictionary<string, string> langsWithKeys = appSelector.SelectedLanguagesWithKeys;
            List<string> selectedLangs = new List<string>();
            foreach(var lang in langsWithKeys) { // reduce number of lopps over this
                if(lang.Value == defaultLanguage) {
                    includedDefaultLanguage = true;
                }
                selectedLangs.Add(lang.Value);
            }
            if(!includedDefaultLanguage) { // the default lang should always be shown
                selectedLangs.Insert(0, defaultLanguage);
            }
            displayedLanguages = selectedLangs.ToArray();
            Session["ro_selectedLanguages"] = displayedLanguages;
        }
        return displayedLanguages;
    }

    /// <summary>
    /// Processes the selected bundles and languages and shows a grid to edit translations for a resource.
    /// Also sets up listeners to block/unclock the UI when AJAX requests are in progress
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void btnEditResources_Click(object sender, EventArgs e) {
        mltMain.ActiveViewIndex = 2;
        Session.Remove("colIndexes");
        //Session.Remove("defaultLanguage");
        InitializeDataSource();
        string addFormListeners = PopulateSelectedLanguages(true, true, true);
        cboAddBundleName.DataBind();
        InitializeGridView();
        grdResources.DataBind();
        ScriptManager.RegisterClientScriptBlock(this, typeof(Page), "ajaxBlock", @"$(function(){ 
            $('#bulktxt').attr('lasttxt', '2');
            $('#navLnkAddBulk').click(function() { addBulk(true); });
            for(var i=0;i<5;i++) { addBulkRow(); }
            $(""#""+GetClientId(""btnAddMoreRows"")).click(function() {addBulkRow();return false;});
            var prm = Sys.WebForms.PageRequestManager.getInstance();
            prm.add_endRequest(function() { 
                $.unblockUI(); attachBlockListeners();
                $(""#""+GetClientId(""btnAddMoreRows"")).click(function() {
                    addBulkRow();
                    return false;}); 
                $('#bulktxt').attr('lasttxt', '2');
                for(var i=0;i<5;i++) { addBulkRow(); } 
            });
            prm.add_initializeRequest(function(sender, args) { $.blockUI({ message: $('#loadingMessage') }); });
            attachBlockListeners();
            $().ajaxStop($.unblockUI);
            $.blockUI.defaults.css = {};
            $('#notResponding').click(function() { $.unblockUI(); return false; });
            " + addFormListeners + "});\n", true);
    }

    /// <summary>
    /// Adds a new element from the form.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void btnAddElement_Click(object sender, EventArgs e) {
        lblAddError.Visible = false;
        string bundleId = cboAddBundleName.SelectedValue,
               elementName = txtAddElementName.Text,
               elementComment = txtAddElementComment.Text,
               resourceComment = txtAddResourceComment.Text;
        int resourceID = int.Parse(cboAddResourceId.SelectedValue);
        if(string.IsNullOrEmpty(elementName)) {
            lblAddError.Text = Resources.Errors.ElementNameNotBlank;
            lblAddError.Visible = true;
        }
        try {
            RBElement ele = new RBElement(-1, elementName, bundleId, null, elementComment) {
                Application = GetSelectedApplication()
            };
            RBResource res = new RBResource(resourceID, resourceComment);

            foreach(string key in Request.Form.AllKeys) {
                if(!key.EndsWith("comment") && key.Contains("txtDyn") && Request.Form[key].Length > 0) {
                    string language = key.Substring(key.LastIndexOf("txtDyn") + 6); // the ID of the textbox has $country-CODE appended to it
                    res.Translations.Add(language, new RBTranslation() {
                        Translation = Request.Form[key],
                        Comment = Request.Form[key + "-comment"],
                        LanguageCode = language
                    });
                }
            }
            ele.Resource = res;
            ResourceBlenderDAL.SaveElement(ele, resourceID, bundleId);
            InitializeDataSource();
            InitializeGridView();
            grdResources.DataBind();

            cboAddBundleName.SelectedIndex = 0;
            txtAddElementName.Text = string.Empty;
            txtAddElementComment.Text = string.Empty;
            txtAddResourceComment.Text = string.Empty;
            PopulateSelectedLanguages(true, false, false);
            pnlAddedElement.Visible = true;
            litAddedElement.Text = string.Format("<p>Added element {0}.</p>", elementName);
            jQueryBuffer.AppendFormat("$('#{0}').slideUp(3000);\n", pnlAddedElement.ClientID)
                        .AppendFormat("$('#{0}').focus();", txtAddElementName.ClientID);
        } catch {
            lblAddError.Text = Resources.Errors.ElementNameExists;
            lblAddError.Visible = true;
            txtAddElementName.Focus();
            InitializeGridView();
            grdResources.DataBind();
        }
    }

    /// <summary>
    /// Adds a batch of elements based on their default language translations.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void btnBulkAdd_Click(object sender, EventArgs e) {
        List<string> newTranslations = new List<string>();
        string applicationId = GetSelectedApplication().ID,
               elementLanguage = cboBulkLanguage.SelectedValue,
               bundle = cboBulkBundle.SelectedValue;
        foreach(string key in Request.Form.AllKeys) {
            if(key.StartsWith("txtBulk")) { // because the textboxes are added with JavaScript is dynamic, the controls have to be grabbed from Request.Form
                string translation = Request.Form[key];
                if(translation.Trim().Length > 0) {
                    newTranslations.Add(translation);
                }
            }
        }
        try {
            if(newTranslations.Count == 0) {
                throw new ResourceBlenderException("No elements found");
            }
            ResourceBlenderDAL.BulkAddElements(applicationId, bundle, elementLanguage, newTranslations, null);

            InitializeDataSource();
            InitializeGridView();
            grdResources.DataBind();
        } catch(Exception ex) {
            throw;// todo: add extra view to multiview to show all errors on this page
        }
    }

    /// <summary>
    /// Sets the select parameter of the DataSource to the selected languages.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.Web.UI.WebControls.ObjectDataSourceSelectingEventArgs"/> instance containing the event data.</param>
    protected void dsMain_Selecting(object sender, ObjectDataSourceSelectingEventArgs e) {
        e.InputParameters[2] = GetSelectedLanguages();
    }

    /// <summary>
    /// Records the total number of records in the DataSource.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.Web.UI.WebControls.SqlDataSourceStatusEventArgs"/> instance containing the event data.</param>
    protected void dsMain_Selected(object sender, ObjectDataSourceStatusEventArgs e) {
        Session["resources"] = (e.ReturnValue as DataTable).Rows.Count;
    }

    /// <summary>
    /// Initializes the ResourceID and main DataSources taking the selected languages and bundles into account.
    /// </summary>
    private void InitializeDataSource() {
        FilterGrid();
       
        string applicationId = appSelector.SelectedApplicationID;
        RBApplication app = GetSelectedApplication();
        string displayName = app.DisplayName,
               defaultLanguage = app.DefaultLanguage;
        lblEditingResourcesFor.Text = string.Format(Resources.UIStrings.EditingResourcesForName, displayName);
        lblBreadcrumbEnd.Text = lblEditingResourcesFor.Text;

        string[] displayedLanguages = GetSelectedLanguages();
        cboBulkLanguage.DataSource = appSelector.SelectedLanguagesWithKeys;
        cboBulkLanguage.DataBind();
        cboAddBundleName.DataBind();
        cboBulkBundle.DataBind();
    }


    /// <summary>
    /// Sets up the column generator for the resource grid using the selected languages.
    /// </summary>
    private void InitializeGridView() {
        Dictionary<string, int> columnIndex = new Dictionary<string, int>();
        string defaultLanguage = GetSelectedApplication().DefaultLanguage;
        List<string> tmpLanguages = appSelector.SelectedLanguages;
        tmpLanguages.Sort();
        tmpLanguages.Remove(defaultLanguage);
        tmpLanguages.Insert(0, defaultLanguage);
        string[] languages = tmpLanguages.ToArray();
        for(int i = 0; i < languages.Length; i++) {
            columnIndex[languages[i]] = i + editButtonColumNo + 1;
        }
        Session["colIndexes"] = columnIndex;
        grdResources.ColumnsGenerator = new LanguageColumnGenerator(languages, defaultLanguage, true);
    }

    /// <summary>
    /// Handles the PageIndexChanging event of the resource grid.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.Web.UI.WebControls.GridViewPageEventArgs"/> instance containing the event data.</param>
    protected void grd_PageIndexChanging(object sender, GridViewPageEventArgs e) {
        grdResources.PageIndex = e.NewPageIndex;
        grdResources.DataBind();
    }

    /// <summary>
    /// Registers JQuery listeners and shows the total number of translations if the grid is paged.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void grd_PreRender(object sender, EventArgs e) {
        base.OnPreRender(e);
        GridViewRow pagerRow = grdResources.TopPagerRow;
        Literal lblNumPages = new Literal();
        if(!grdResources.IsEmpty) {
            TableCell cell = pagerRow.Cells[0];
            (cell.Controls[0] as Table).CssClass = "inline";
            if(grdResources.Rows.Count > 0) {
                int numRecords = 0; // SqlDataSource's Select event is not fired when filtering
                int.TryParse((Session["resources"] ?? "0").ToString(), out numRecords);
                lblNumPages.Text = string.Format("<div class=\"itemCount\">" + Resources.UIStrings.ElementCountStatus + "</div>", grdResources.Rows[0].DataItemIndex + 1, grdResources.Rows[grdResources.Rows.Count - 1].DataItemIndex + 1, Math.Min(grdResources.Rows.Count * grdResources.PageCount, numRecords));
                cell.Controls.Add(lblNumPages);
                lblNoMatching.Visible = false;
            }
        } else {
            lblNoMatching.Visible = true;
        }
        if(grdResources.SelectedIndex >= 0 && grdResources.Rows.Count > grdResources.SelectedIndex) {
            GridViewRow row = grdResources.Rows[grdResources.SelectedIndex];
            // The control indexes of the textbox and image buttons to which to attach event listeners
            TableCell cell = row.Cells[2];
            TextBox txt = cell.FindControl("txtElementComment") as TextBox;
            HyperLink btnEditPO = cell.FindControl("btnEditPO") as HyperLink,
                      btnFindSimilar,
                      btnGoogle;
            Utils.AssignEditPOListener(jQueryBuffer, btnEditPO.ClientID, txt.ClientID);
            string rowId,
                   defaultLang = GetSelectedApplication().DefaultLanguage,// Session["defaultLanguage"].ToString(),
                   defaultLangTxtBoxId = row.Cells[editButtonColumNo + 1].Controls[SelectedRowControlIndexes.txtTranslation].ClientID, // the ID of the textbox containing the source (default) language
                   defaultLangGoogleCode = string.Empty; // Google's code for the default language
            try {
                defaultLangGoogleCode = Utils.ISOCode2GoogleCode(CultureInfo.GetCultureInfo(defaultLang).TwoLetterISOLanguageName);
            } catch { }
            for(int i = editButtonColumNo + 1; i < row.Cells.Count; i++) {
                rowId = row.Cells[i].ClientID;
                cell = row.Cells[i];
                txt = cell.Controls[SelectedRowControlIndexes.txtTranslation] as TextBox;
                btnFindSimilar = cell.Controls[SelectedRowControlIndexes.btnFindSimilar] as HyperLink;
                btnGoogle = cell.Controls[SelectedRowControlIndexes.btnGoogle] as HyperLink;
                Utils.AssignFindSimilarListener(jQueryBuffer, btnFindSimilar.ClientID, txt.ClientID, rowId, btnFindSimilar.NavigateUrl.Substring(1), false);
                if(btnGoogle != null && btnGoogle.Enabled) {
                    Utils.AssignGoogleTranslateSimilarListener(jQueryBuffer, btnGoogle.ClientID, defaultLangTxtBoxId, defaultLangGoogleCode, txt.ClientID, btnGoogle.NavigateUrl.Substring(1));
                }
            }
            System.Diagnostics.Debug.WriteLine("prerender");
        }
        // the add form is also in the update panel, so re-register the click events
        Utils.AssignEditPOListener(jQueryBuffer, btnAddEditPO.ClientID, txtAddElementComment.ClientID);
        for(int i=0;i<tblAddResource.Rows.Count;i++) {
            TableRow addRow = tblAddResource.Rows[i];
            TableCell celLanguage = addRow.Cells[0];
            Label lbl = celLanguage.Controls[0] as Label;
            string abbrev = lbl.ID.Replace("lblDyn", "");
            TableCell celResource = addRow.Cells[1];
            TextBox addTxt = celResource.Controls[AddFormControlIndexes.txtTranslation] as TextBox;
            HyperLink btnFindResource = celResource.Controls[AddFormControlIndexes.btnFindSimilar] as HyperLink;
            Utils.AssignFindSimilarListener(jQueryBuffer, btnFindResource.ClientID, addTxt.ClientID, celResource.ClientID, abbrev, true);
        }
    }

    /// <summary>
    /// Makes the EditIndex the SelectedIndex.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.Web.UI.WebControls.GridViewEditEventArgs"/> instance containing the event data.</param>
    protected void grd_RowEditing(object sender, GridViewEditEventArgs e) {
        GridViewRow row = grdResources.Rows[e.NewEditIndex];
        grdResources.SelectedIndex = row.RowIndex;
    }

    /// <summary>
    /// Databinds the BundleName and ResourceID comboboxes and assigns JavaScript listeners.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.Web.UI.WebControls.GridViewRowEventArgs"/> instance containing the event data.</param>
    protected void grd_RowDataBound(object sender, GridViewRowEventArgs e) {
        if(e.Row.RowType == DataControlRowType.DataRow) {
            if(((e.Row.RowState & DataControlRowState.Edit) == DataControlRowState.Edit)) {
                GridViewRow row = e.Row;
                row.Cells[2].CssClass = "ssCell"; // remove the comment indicators
                row.Cells[3].CssClass = "ssCell";

                int textBoxControlIndex = row.Cells[editButtonColumNo + 1].Controls.Count - 1;// the comments textbox is the last control in the view
                int resourceid = int.Parse(DataBinder.Eval(e.Row.DataItem, "resourceid").ToString());

                // load translator comments
                string[] displayedLanguages = GetSelectedLanguages();
                Dictionary<string, int> columnIndex = Session["colIndexes"] as Dictionary<string, int>;
                Dictionary<string, string> translatorComments = ResourceBlenderDAL.GetTranslatorComments(resourceid, displayedLanguages);
                foreach(var comment in translatorComments) {
                    (row.Cells[columnIndex[comment.Key]].Controls[textBoxControlIndex] as TextBox).Text = comment.Value;
                }

                DataRowView view = row.DataItem as DataRowView;
                DropDownList cboBundleName = row.FindControl("cboBundleName") as DropDownList;
                cboBundleName.SelectedValue = view["bundle"].ToString();

                DropDownList cboResourceId = row.FindControl("cboResourceID") as DropDownList;
                cboResourceId.SelectedValue = view["resourceid"].ToString();

                // find the JS callback which is assigned to the 'Change Resource' button
                Button btnChangeResource = row.FindControl("btnChangeResource") as Button;
                string jsChangeResource = ClientScript.GetPostBackClientHyperlink(btnChangeResource, "");
                cboResourceId.Attributes["onchange"] = jsChangeResource; // assign the callback to the dropdown list so a button press is simulated when the index changes
            } else {
                string elementComment = (DataBinder.Eval(e.Row.DataItem, "elementcomment") ?? string.Empty).ToString(),
                       resourceComment = (DataBinder.Eval(e.Row.DataItem, "resourcecomment") ?? string.Empty).ToString();
                if(elementComment.Length > 0) {
                    e.Row.Cells[2].ToolTip = elementComment;
                    e.Row.Cells[2].CssClass = "ssCell commentCell";
                }
                if(resourceComment.Length > 0) {
                    e.Row.Cells[3].ToolTip = resourceComment;
                    e.Row.Cells[3].CssClass = "ssCell commentCell";
                }
            }
        }
    }

    /// <summary>
    /// Handles the Sorting event of the resource grid.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.Web.UI.WebControls.GridViewSortEventArgs"/> instance containing the event data.</param>
    protected void grd_Sorting(object sender, GridViewSortEventArgs e) {
        // Multicolumn sort disabled for now
        /*System.Diagnostics.Debug.WriteLine(e.SortDirection + "-" + e.SortExpression);
        string oldExpression = grdResources.SortExpression,
               newExpression = e.SortExpression;
        if(oldExpression.IndexOf(newExpression) < 0) { // ie: old doesn't contain new
            if(oldExpression.Length > 0) {
                e.SortExpression = newExpression + "," + oldExpression;
            } else {
                e.SortExpression = newExpression;
            }
        } else {
            e.SortExpression = oldExpression;
        }*/
    }

    /// <summary>
    /// Handles RowCommand events from the resource grid.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.Web.UI.WebControls.GridViewCommandEventArgs"/> instance containing the event data.</param>
    protected void grd_RowCommand(object sender, GridViewCommandEventArgs e) {
        InitializeDataSource();
        InitializeGridView();
        GridViewRow row = null;  // The row which has clicked was the un-databound one, so use it's index to lookup the new, databound row
        switch(e.CommandName) {
            case "editresource": // 'Edit' button clicked
                int rowIndex = ((e.CommandSource as WebControl).NamingContainer as GridViewRow).RowIndex;
                if(rowIndex < grdResources.Rows.Count) {
                    row = grdResources.Rows[rowIndex];
                    grdResources.SelectedIndex = row.RowIndex;
                    grdResources.EditIndex = row.RowIndex;
                }
                break;
            case "canceledit":
                row = grdResources.Rows[((e.CommandSource as WebControl).NamingContainer as GridViewRow).RowIndex];
                CancelEditing();
                break;
            case "saveresource": // 'Save' button clicked
                grdResources.DataBind();
                row = (e.CommandSource as WebControl).NamingContainer as GridViewRow;
                UpdateResource(row, int.Parse(e.CommandArgument.ToString()));
                break;
            case "deleteelement":
                row = grdResources.Rows[((e.CommandSource as WebControl).NamingContainer as GridViewRow).RowIndex];
                CancelEditing();
                grdResources.PreRender += (s, pree) => {
                    GridViewRow newRrow = grdResources.Rows[row.RowIndex];
                    (newRrow.FindControl("mltActions") as MultiView).ActiveViewIndex = 1;
                };
                break;
            case "confirmdelete":
                DeleteElement(e.CommandArgument.ToString());
                break;
        }
    }

    /// <summary>
    /// Deletes the specified element.
    /// </summary>
    /// <param name="elementID">The element ID.</param>
    private void DeleteElement(string elementID) {
        ResourceBlenderDAL.DeleteElement(GetSelectedApplication().ID, elementID);
        grdResources.DataBind();
    }

    /// <summary>
    /// When the index changes, this event is fired before the RowCommand handler kicks in and takes the hidden button click.
    /// After RowCommand, the rows are re-databound. In this handler, another handler is hooked up for pre-render to change the values to the displayed resource.
    /// Summary: SelectedIndexChanged -> RowCommand -> RowDataBound -> PreRender
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void cboResourceID_SelectedIndexChanged(object sender, EventArgs e) {
        DropDownList cbo = sender as DropDownList;
        GridViewRow row = cbo.NamingContainer as GridViewRow;
        int selectedResource = int.Parse(cbo.SelectedValue); // Index that was just selected.
        int rowIndex = row.RowIndex; // the row index, the index doesn't change after databinding
        
        // preserve the values entered but not yet saved, because after databinding they'll be lost. Re-set them later
        string currentBundle = (row.Cells[1].FindControl("cboBundleName") as DropDownList).SelectedValue;
        string currentElementName = (row.Cells[2].FindControl("txtElementName") as TextBox).Text;
        string currentElementComment = (row.Cells[2].FindControl("txtElementComment") as TextBox).Text;
        RBElement ele = new RBElement(-1, currentElementName, currentBundle, null, currentElementComment);
        RBResource res = ResourceBlenderDAL.GetResource(selectedResource, GetSelectedLanguages());
        ele.Resource = res;
        PreserveElement(rowIndex, ele, res);
        
    }

    /// <summary>
    /// Preserves an element through DataBinding by adding it after PreRender.
    /// </summary>
    /// <param name="rowIndex">Index of the row in the GridView.</param>
    /// <param name="ele">The element.</param>
    /// <param name="res">The resource.</param>
    private void PreserveElement(int rowIndex, RBElement element, RBResource resource) {
        grdResources.PreRender += (s, pree) => {
            GridViewRow row = grdResources.Rows[rowIndex];// the new row, after databinding
            DropDownList newCbo = row.FindControl("cboResourceID") as DropDownList;
            newCbo.SelectedValue = resource.ID.ToString();
            if(row.Cells.Count <= 4) // row unbound for some reasom
                return;

            (row.Cells[1].FindControl("cboBundleName") as DropDownList).SelectedValue = element.BundleID;
            (row.Cells[2].FindControl("txtElementName") as TextBox).Text = element.Name;
            (row.Cells[2].FindControl("txtElementComment") as TextBox).Text = element.Comment;

            int txtCommentControlIndex = row.Cells[editButtonColumNo + 1].Controls.Count - 1;// the comments textbox is the last control in the view
            if(resource.ID < 0) {
                View viwEdit;
                for(int i = editButtonColumNo + 1; i < row.Cells.Count; i++) {
                    viwEdit = (row.Cells[i].Controls[0] as MultiView).Views[1];
                    (viwEdit.Controls[SelectedRowControlIndexes.txtTranslation] as TextBox).Text = "";
                    (viwEdit.Controls[txtCommentControlIndex] as TextBox).Text = "";
                }
            } else {
                Dictionary<string, int> columnIndex = Session["colIndexes"] as Dictionary<string, int>;
                foreach(var translation in resource.Translations) {
                    TableCell cell = row.Cells[columnIndex[translation.Key]]; // the cell with the edit controls
                    (cell.Controls[AddFormControlIndexes.txtTranslation] as TextBox).Text = translation.Value.Translation;

                    // fill the translators' comment boxes
                    (cell.Controls[txtCommentControlIndex] as TextBox).Text = translation.Value.Comment;
                }
                (row.Cells[editButtonColumNo].FindControl("txtResourceComment") as TextBox).Text = resource.Comment;
            }
        };
    }

    /// <summary>
    /// Changes the resource displayed on the 'New Element' form.
    /// </summary>
    /// <param name="sender">The event source.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void ChangeAddDisplayedResource(object sender, EventArgs e) {
        int selectedResource = int.Parse(cboAddResourceId.SelectedValue);
        InitializeDataSource();
        InitializeGridView();
        grdResources.DataBind();

        RBResource res = ResourceBlenderDAL.GetResource(selectedResource, GetSelectedLanguages());
        Dictionary<string, int> columnIndex = Session["colIndexes"] as Dictionary<string, int>;
        foreach(var translation in res.Translations) {
            TableRow translationRow = tblAddResource.Rows[columnIndex[translation.Key] - editButtonColumNo - 1];
            (translationRow.Cells[1].Controls[AddFormControlIndexes.txtTranslation] as TextBox).Text = translation.Value.Translation;
            (translationRow.Cells[2].Controls[AddFormControlIndexes.txtTranslationComment] as TextBox).Text = translation.Value.Comment; // fill the translators' comments
        }
        txtAddResourceComment.Text = res.Comment;
        cboAddResourceId.SelectedValue = selectedResource.ToString();
    }

    /// <summary>
    /// Updates a resource.
    /// </summary>
    /// <param name="oldRow">The old row.</param>
    /// <param name="id">The id.</param>
    private void UpdateResource(GridViewRow oldRow, int id) {
        GridViewRow newRow = grdResources.Rows[oldRow.RowIndex];
        Label lblError = newRow.Cells[editButtonColumNo].FindControl("lblError") as Label;
        string bundleId = (oldRow.Cells[1].FindControl("cboBundleName") as DropDownList).SelectedValue,
               elementName = (oldRow.Cells[2].FindControl("txtElementName") as TextBox).Text,
               elementComment = (oldRow.Cells[2].FindControl("txtElementComment") as TextBox).Text,
               resourceComment = (oldRow.Cells[3].FindControl("txtResourceComment") as TextBox).Text;
        int resourceID = int.Parse((oldRow.Cells[3].FindControl("cboResourceID") as DropDownList).SelectedValue);

        RBElement ele = new RBElement(id, elementName, bundleId, "UpdateResource-ROnline.aspx", elementComment);
        RBResource res = new RBResource(resourceID, resourceComment);

        string textBoxID, language;
        Dictionary<string, int> columnIndex = Session["colIndexes"] as Dictionary<string, int>;
        for(int i = editButtonColumNo + 1; i < newRow.Cells.Count; i++) {
            textBoxID = newRow.Cells[i].Controls[1].UniqueID;
            foreach(string key in Request.Form.AllKeys) {
                if(key == textBoxID && Request.Form[key].Length > 0) { // because the gridview is dynamic, the controls have to be grabbed from Request.Form
                    language = textBoxID.Substring(textBoxID.LastIndexOf("$") + 1); // the ID of the textbox has $country-CODE appended to it
                    res.Translations.Add(language, new RBTranslation() {
                        Translation = Request.Form[key],
                        LanguageCode = language,
                        Comment = Request.Form[textBoxID + "-comment"]
                    });
                    break;
                }
            }
        }
        ele.Resource = res;
        try {
            ResourceBlenderDAL.SaveElement(ele, resourceID, bundleId);
            CancelEditing();
            cboAddResourceId.DataBind();
            grdResources.DataBind();
        } catch {
            lblError.Text = Resources.Errors.ElementNameExists;
            lblError.Visible = true;
            PreserveElement(oldRow.RowIndex, ele, res);
        }
        System.Diagnostics.Debug.WriteLine("1");
    }

    #region Filtering
    /// <summary>
    /// Rebinds the grid ( filtering is done during InitializeGridView() ).
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void btnApplyFilters_Click(object sender, EventArgs e) {
        InitializeDataSource();
        InitializeGridView();
        grdResources.DataBind();
        CancelEditing();
    }

    /// <summary>
    /// Removes all filters.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void btnRemoveFilters_Click(object sender, EventArgs e) {
        txtBundleFilter.Text = string.Empty;
        txtElementFilter.Text = string.Empty;
        txtElementCommentFilter.Text = string.Empty;
        txtResourceCommentFilter.Text = string.Empty;
        txtTranslationFilter.Text = string.Empty;
        cboBundleFilter.SelectedIndex = 0;
        cboElementFilter.SelectedIndex = 0;
        cboElementCommentFilter.SelectedIndex = 0;
        cboResourceCommentFilter.SelectedIndex = 0;
        cboTranslationFilter.SelectedIndex = 0;
        cboTranslationFilterLanguage.SelectedIndex = 0;
        InitializeDataSource();
        InitializeGridView();
        grdResources.DataBind();
        CancelEditing();
    }

    /// <summary>
    /// Shows/Hides the filters panel
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void lnkToggleFilters_Click(object sender, EventArgs e) {
        if(pnlFiltersCollapsable.CssClass == "collapsed") {
            pnlFiltersCollapsable.CssClass = "";
            imgToggleFilters.ImageUrl = ResolveUrl("~/images/arrow_up.gif");
        } else {
            pnlFiltersCollapsable.CssClass = "collapsed";
            imgToggleFilters.ImageUrl = ResolveUrl("~/images/arrow_down.gif");
        }
        InitializeDataSource();
        InitializeGridView();
        grdResources.DataBind();
        CancelEditing();
    }

    /// <summary>
    /// Cancels any editing in progress in the grid and hides all edit controls.
    /// </summary>
    private void CancelEditing() {
        if(grdResources.SelectedIndex >= 0 && grdResources.SelectedIndex < grdResources.Rows.Count) {
            grdResources.SelectedIndex = -1;
            grdResources.EditIndex = -1;
        }
    }

    /// <summary>
    /// Applies filters to the datasource.
    /// </summary>
    private void FilterGrid() {
        dsMain.FilterParameters.Clear();
        int numFilters = 0;
        StringBuilder filterBuffer = new StringBuilder();
        string bundle = txtBundleFilter.Text,
               element = txtElementFilter.Text,
               elementComment = txtElementCommentFilter.Text,
               resourceComment = txtResourceCommentFilter.Text,
               translation = txtTranslationFilter.Text;
        if(!string.IsNullOrEmpty(bundle)) {
            filterBuffer.Append("bundlename like ");
            switch(cboBundleFilter.SelectedIndex) {
                case 0:
                    filterBuffer.Append("'%{").Append(numFilters++).Append("}%'");
                    break;
                case 1:
                    filterBuffer.Append("'{").Append(numFilters++).Append("}%'");
                    break;
                case 2:
                    filterBuffer.Append("'%{").Append(numFilters++).Append("}'");
                    break;
            }
            dsMain.FilterParameters.Add("bundlename", TypeCode.String, bundle);
        }
        if(!string.IsNullOrEmpty(element)) {
            if(filterBuffer.Length > 0) {
                filterBuffer.Append(" and ");
            }
            filterBuffer.Append("elementname like ");
            switch(cboElementFilter.SelectedIndex) {
                case 0:
                    filterBuffer.Append("'%{").Append(numFilters++).Append("}%'");
                    break;
                case 1:
                    filterBuffer.Append("'{").Append(numFilters++).Append("}%'");
                    break;
                case 2:
                    filterBuffer.Append("'%{").Append(numFilters++).Append("}'");
                    break;
            }
            dsMain.FilterParameters.Add("elementname", TypeCode.String, element);
        }
        if(!string.IsNullOrEmpty(elementComment)) {
            if(filterBuffer.Length > 0) {
                filterBuffer.Append(" and ");
            }
            filterBuffer.Append("elementcomment like ");
            switch(cboElementCommentFilter.SelectedIndex) {
                case 0:
                    filterBuffer.Append("'%{").Append(numFilters++).Append("}%'");
                    break;
                case 1:
                    filterBuffer.Append("'{").Append(numFilters++).Append("}%'");
                    break;
                case 2:
                    filterBuffer.Append("'%{").Append(numFilters++).Append("}'");
                    break;
            }
            dsMain.FilterParameters.Add("elementcomment", TypeCode.String, elementComment);
        }
        if(!string.IsNullOrEmpty(resourceComment)) {
            if(filterBuffer.Length > 0) {
                filterBuffer.Append(" and ");
            }
            filterBuffer.Append("resourcecomment like ");
            switch(cboResourceCommentFilter.SelectedIndex) {
                case 0:
                    filterBuffer.Append("'%{").Append(numFilters++).Append("}%'");
                    break;
                case 1:
                    filterBuffer.Append("'{").Append(numFilters++).Append("}%'");
                    break;
                case 2:
                    filterBuffer.Append("'%{").Append(numFilters++).Append("}'");
                    break;
            }
            dsMain.FilterParameters.Add("resourcecomment", TypeCode.String, resourceComment);
        }
        if(cboTranslationFilter.SelectedIndex == 3) {
            if(filterBuffer.Length > 0) {
                filterBuffer.Append(" and ");
            }
            filterBuffer.Append('[').Append(cboTranslationFilterLanguage.SelectedValue).Append("] IS NULL");
        } else if(!string.IsNullOrEmpty(translation)) {
            if(filterBuffer.Length > 0) {
                filterBuffer.Append(" and ");
            }
            filterBuffer.Append('[').Append(cboTranslationFilterLanguage.SelectedValue).Append("] like ");
            switch(cboTranslationFilter.SelectedIndex) {
                case 0:
                    filterBuffer.Append("'%{").Append(numFilters++).Append("}%'");
                    break;
                case 1:
                    filterBuffer.Append("'{").Append(numFilters++).Append("}%'");
                    break;
                case 2:
                    filterBuffer.Append("'%{").Append(numFilters++).Append("}'");
                    break;
            }
            dsMain.FilterParameters.Add(cboTranslationFilterLanguage.SelectedValue, TypeCode.String, translation);
        }
        dsMain.FilterExpression = filterBuffer.ToString();
    }
    #endregion

    /// <summary>
    /// Switches back to the first form, retaining the selected values.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void lnkNavEditOnline_Click(object sender, EventArgs e) {
        mltMain.ActiveViewIndex = 1;
    }

    /// <summary>
    /// Expands/contracts the grid to fill the whole window
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void lnkExpandScreen_Click(object sender, EventArgs e) {
        if(fullsizeholder.Attributes["class"] == "ssFullSize") {
            lnkExpandScreen.Text = Resources.UIStrings.ExpandFillWindow;
            lnkExpandScreen.CssClass = "lnkExpand";
            fullsizeholder.Attributes["class"] = "ssContracted";
        } else {
            lnkExpandScreen.Text = Resources.UIStrings.ContractNormalSize;
            lnkExpandScreen.CssClass = "lnkContract";
            fullsizeholder.Attributes["class"] = "ssFullSize";
        }
    }
    
    
}
